import datetime

from flask import current_app, jsonify
from sqlalchemy import desc
from library.api.tFlask import tflask
from apps.interface.settings import config
from apps.interface.models.interfacecase import InterfaceCase
from apps.interface.models.interfacecaseset import InterfaceCaseSet
from apps.interface.models.interfaceproject import InterfaceProject
from apps.interface.models.interfacetask import InterfaceTask
from apps.interface.util.email.SendEmail import SendEmail
from apps.interface.util.global_variable import TEMP_REPORT
from apps.interface.util.http_run import RunCase
from apps.interface.util.report.report import render_html_report
from apps.interface.util.utils import *
from library.api.db import db
from library.api.transfer import transfer2json
from library.api.redis import RedisLock
from library.api.db import t_redis
from public_config import REDIS_TASK_NAME, REDIS_TASK_QUEUE, REDIS_TASK_NUMBER
from apps.project.business.project import ProjectBusiness
from apps.interface.util.dingding import InterfaceCasePush


class InterfaceTaskBusiness(object):
    
    @classmethod
    def _query(cls):
        return InterfaceTask.query.add_columns(
            InterfaceTask.id.label('id'),
            InterfaceTask.task_name.label('task_name'),
            InterfaceTask.task_config_time.label('task_config_time'),
            InterfaceTask.set_id.label('set_id'),
            InterfaceTask.case_id.label('case_id'),
            InterfaceTask.task_type.label('task_type'),
            InterfaceTask.task_to_email_address.label('task_to_email_address'),
            InterfaceTask.task_send_email_address.label('task_send_email_address'),
            InterfaceTask.status.label('status'),
            InterfaceTask.project_id.label('project_id'),
            InterfaceTask.delete_status.label('delete_status'),
        )
    
    @classmethod
    @transfer2json(
        '?id|!task_name|!task_config_time|!set_id|!case_id|!task_type|!task_to_email_address|'
        '!task_send_email_address|!status|!project_id|!delete_status')
    def query_all_json(cls, limit, offset):
        ret = cls._query().filter(InterfaceTask.delete_status == InterfaceTask.ACTIVE) \
            .order_by(desc(InterfaceTask.id)) \
            .limit(limit).offset(offset).all()
        return ret
    
    @classmethod
    def task_create(cls, task_name, task_config_time, set_id, case_id, task_type,
                    task_to_email_address,
                    task_send_email_address, status, project_id):
        try:
            m = InterfaceTask(
                task_name=task_name,
                task_config_time=task_config_time,
                set_id=set_id,
                case_id=case_id,
                task_type=task_type,
                task_to_email_address=task_to_email_address,
                task_send_email_address=task_send_email_address,
                status=status,
                project_id=project_id,
            )
            db.session.add(m)
            db.session.commit()
            return 0, None
        except Exception as e:
            current_app.logger.error(str(e))
            return 102, str(e)
    
    @classmethod
    def task_delete(cls, id):
        try:
            m = InterfaceTask.query.get(id)
            m.delete_status = InterfaceTask.DISABLE
            db.session.add(m)
            db.session.commit()
            return 0
        except Exception as e:
            current_app.logger.error(str(e))
            return 105, str(e)
    
    @classmethod
    def task_modify(cls, id, task_name, task_config_time, set_id, case_id, task_type,
                    task_to_email_address,
                    task_send_email_address, status, project_id):
        try:
            m = InterfaceTask.query.get(id)
            m.task_name = task_name,
            m.task_config_time = task_config_time,
            m.set_id = set_id,
            m.case_id = case_id,
            m.task_type = task_type,
            m.task_to_email_address = task_to_email_address,
            m.task_send_email_address = task_send_email_address,
            m.status = status,
            m.project_id = project_id,
            db.session.add(m)
            db.session.commit()
            return 0, None
        except Exception as e:
            current_app.logger.error(str(e))
            return 102, str(e)
    
    @classmethod
    def aps_test(cls, project_id, project_name, case_ids, send_address=None, send_password=None,
                 task_to_address=None, task_id=None, task_env=None):
        params = {'project_id': project_id, 'case_ids': case_ids, 'ci': 0}
        redis_lock = RedisLock(t_redis)
        project_uuid = redis_lock.acquire_lock(project_id)
        try:
            if project_uuid:  # 获取到redis锁
                task_total = t_redis.get(REDIS_TASK_NAME)  # 获取当前执行任务数量
                if task_total is None:
                    task_total = 0
                if int(task_total) > REDIS_TASK_NUMBER:  # 如果任务数大于REDIS_TASK_NUMBER
                    t_redis.lpush(REDIS_TASK_QUEUE, json.dumps(params))  # 加入定时任务队列
                    return {'msg': '当前任务太多，已加入队列稍后执行！', 'status': 0, 'data': {}}
                t_redis.incr(REDIS_TASK_NAME, 1)  # 任务总数+1
                app = tflask(config)  # 定时任务需要app上下文
                with app.app_context():
                    current_app.logger.info('project-{}-{}-执行任务开始'.format(project_id, project_uuid))
                    try:
                        project = InterfaceProject.query.filter_by(name=project_name,
                                                                   status=InterfaceProject.ACTIVE).first()
                        d = RunCase(project.id, task_env)
                        jump_res = d.run_case(d.get_case_test(case_ids))
                        # report入库并获取report_id
                        report_id = d.build_report(jump_res, case_ids)
                        
                        res = json.loads(jump_res)
                        current_app.logger.info('send_address:{}'.format(send_address))
                        
                        if send_address:
                            # 获取钉钉token
                            interface_task = InterfaceTask.query.filter_by(id=task_id,
                                                                           delete_status=0).first()
                            if not interface_task:
                                raise Exception('根据task_id查询数据无数据')
                            dd_token = interface_task.dd_token
                            if dd_token:
                                # 查出task_name
                                task_name = interface_task.task_name
                                # 获取all_project_id
                                all_project_id = InterfaceProject.query.filter_by(id=project_id,
                                                                                  status=0).first().all_project_id
                                # 获取项目图片
                                url = ProjectBusiness.get_url(all_project_id)
                                stat = res.get('stat')
                                result = {
                                    'report_id': report_id,
                                    'url': url,
                                    'project_id': project_id,
                                    'project_name': project_name,
                                    'dd_token': dd_token,
                                    'all_scene': stat.get('all_scene'),
                                    'successes': stat.get('successes_scene1'),
                                    'failures': stat.get('failures_scene1'),
                                    'task_name': task_name
                                }
                                InterfaceCasePush.interface_push_markdown(result)
                            # 处理不是debug模式运行路径问题
                            if TEMP_REPORT.find('/apps/interface') == -1:
                                report_path = TEMP_REPORT.replace('/util/report',
                                                                  '/apps/interface/util/report')
                            else:
                                report_path = TEMP_REPORT
                            
                            task_to_address = task_to_address.split(',')
                            file = render_html_report(res,
                                                      html_report_name='{}接口自动化测试报告'.format(
                                                          datetime.datetime.now().strftime(
                                                              '%Y-%m-%d-%H-%M-%S')),
                                                      html_report_template=r'{}/extent_report_template.html'.format(
                                                          report_path),
                                                      data_or_report=False)
                            s = SendEmail(send_address, send_password, task_to_address, file)
                            s.send_email(name=project.name, content=res)
                        t_redis.decr(REDIS_TASK_NAME, 1)  # 任务总数-1
                        return {'msg': '测试成功', 'status': 1, 'data': {'report_id': d.new_report_id}}
                    except Exception as e:
                        current_app.logger.error(
                            'project-{}-{}执行任务出错了 {}'.format(project_id, project_uuid, str(e)))
                        t_redis.decr(REDIS_TASK_NAME, 1)  # 任务总数-1
                        return {'msg': '执行用例失败！', 'status': 0, 'data': {}}
            else:
                return {'msg': '执行用例失败，有任务在执行获取锁失败！', 'status': 0, 'data': {}}
        finally:
            if project_uuid:
                redis_lock.release_lock(project_id, project_uuid)  # 释放redis锁
    
    @classmethod
    def run_task(cls, ids):
        _data = InterfaceTask.query.filter_by(id=ids, delete_status=InterfaceTask.ACTIVE).first()
        case_ids = []
        if len(json.loads(_data.case_id)) != 0:
            case_ids += [i['id'] for i in json.loads(_data.case_id)]
        else:
            if len(json.loads(_data.case_id)) == 0 and len(json.loads(_data.set_id)) == 0:
                project_id = InterfaceProject.query.filter_by(name=_data.project_id,
                                                              status=InterfaceProject.ACTIVE).first().id
                _set_ids = [_set.id for _set in
                            InterfaceCaseSet.query.filter_by(project_id=project_id,
                                                             status=InterfaceCaseSet.ACTIVE).order_by(
                                InterfaceCaseSet.num.asc()).all()]
            else:
                _set_ids = [i['id'] for i in json.loads(_data.set_id)]
            for set_id in _set_ids:
                for case_data in InterfaceCase.query.filter_by(
                        case_set_id=set_id, status=InterfaceCase.ACTIVE).order_by(
                    InterfaceCase.num.asc()).all():
                    case_ids.append(case_data.id)
        project_name = InterfaceProject.query.filter_by(id=_data.project_id,
                                                        status=InterfaceProject.ACTIVE).first().name
        result = cls.aps_test(_data.project_id, project_name, case_ids, task_env=_data.task_env)
        
        return jsonify(result)
    
    @classmethod
    def start_task(cls, ids):
        _data = InterfaceTask.query.filter_by(id=ids, delete_status=InterfaceTask.ACTIVE).first()
        config_time = change_cron(_data.task_config_time)
        case_ids = cls.get_case_ids(_data)
        project_name = InterfaceProject.query.filter_by(id=_data.project_id,
                                                        status=InterfaceProject.ACTIVE).first().name
        try:
            current_app.apscheduler.add_job(func=cls.aps_test, trigger='cron',
                                            args=[_data.project_id, project_name, case_ids,
                                                  _data.task_send_email_address,
                                                  _data.email_password,
                                                  _data.task_to_email_address,
                                                  ids, _data.task_env],
                                            id=str(ids), **config_time)  # 添加任务
        except Exception as e:
            current_app.logger.error(str(e))
            return jsonify({'msg': '启动失败请检查cron表达式', 'status': 0})
        _data.status = '启动'
        db.session.commit()
        return jsonify({'msg': '启动成功', 'status': 1})
    
    @classmethod
    def get_case_ids(cls, _data):
        case_ids = []
        if len(json.loads(_data.case_id)) != 0:
            case_ids += [i['id'] for i in json.loads(_data.case_id)]
        else:
            if len(json.loads(_data.case_id)) == 0 and len(json.loads(_data.set_id)) == 0:
                project_id = InterfaceProject.query.filter_by(id=_data.project_id,
                                                              status=InterfaceProject.ACTIVE).first().id
                _set_ids = [_set.id for _set in
                            InterfaceCaseSet.query.filter_by(project_id=project_id,
                                                             status=InterfaceCaseSet.ACTIVE).order_by(
                                InterfaceCaseSet.num.asc()).all()]
            else:
                _set_ids = [i['id'] for i in json.loads(_data.set_id)]
            for set_id in _set_ids:
                for case_data in InterfaceCase.query.filter_by(
                        case_set_id=set_id, status=InterfaceCase.ACTIVE).order_by(
                    InterfaceCase.num.asc()).all():
                    case_ids.append(case_data.id)
        return case_ids
    
    @classmethod
    def add_task(cls, project_name, set_ids, case_ids, task_id, name, to_email, send_email,
                 password, num, time_config, ding_token, task_env):
        task_type = 'cron'
        if not project_name:
            return jsonify({'msg': '请选择项目', 'status': 0})
        project_id = InterfaceProject.query.filter_by(name=project_name,
                                                      status=InterfaceProject.ACTIVE).first().id
        num = auto_num(num, InterfaceTask, project_id=project_id,
                       delete_status=InterfaceTask.ACTIVE)
        # 0 0 1 * * *
        if not (not to_email and not send_email and not password) and not (
                to_email and send_email and password):
            return jsonify({'msg': '发件人、收件人、密码3个必须都为空，或者都必须有值', 'status': 0})
        
        if len(time_config.strip().split(' ')) != 6:
            return jsonify({'msg': 'cron格式错误', 'status': 0})
        
        if task_id:
            old_task_data = InterfaceTask.query.filter_by(id=task_id,
                                                          delete_status=InterfaceTask.ACTIVE).first()
            if InterfaceTask.query.filter_by(
                    task_name=name,
                    delete_status=InterfaceTask.ACTIVE
            ).first() and name != old_task_data.task_name:
                return jsonify({'msg': '任务名字重复', 'status': 0})
            else:
                old_task_data.project_id = project_id
                old_task_data.set_id = json.dumps(set_ids)
                old_task_data.case_id = json.dumps(case_ids)
                old_task_data.task_name = name
                old_task_data.task_type = task_type
                old_task_data.task_to_email_address = to_email
                old_task_data.task_send_email_address = send_email
                old_task_data.email_password = password
                old_task_data.num = num
                old_task_data.dd_token = ding_token
                old_task_data.task_env = task_env
                if old_task_data.status != '创建' and old_task_data.task_config_time != time_config:
                    current_app.apscheduler.reschedule_job(str(task_id), trigger='cron',
                                                           **change_cron(time_config))  # 修改任务
                    old_task_data.status = '启动'
                
                old_task_data.task_config_time = time_config
                db.session.commit()
                return jsonify({'msg': '修改成功', 'status': 1})
        else:
            
            if InterfaceTask.query.filter_by(task_name=name,
                                             delete_status=InterfaceTask.ACTIVE).first():
                return jsonify({'msg': '任务名字重复', 'status': 0})
            else:
                new_task = InterfaceTask(task_name=name,
                                         project_id=project_id,
                                         set_id=json.dumps(set_ids),
                                         case_id=json.dumps(case_ids),
                                         email_password=password,
                                         task_type=task_type,
                                         task_to_email_address=to_email,
                                         task_send_email_address=send_email,
                                         task_config_time=time_config,
                                         num=num,
                                         dd_token=ding_token,
                                         task_env=task_env,
                                         name='0')
                db.session.add(new_task)
                db.session.commit()
                return jsonify({'msg': '新建成功', 'status': 1})
    
    @classmethod
    def edit_task(cls, task_id):
        c = InterfaceTask.query.filter_by(id=task_id, delete_status=InterfaceTask.ACTIVE).first()
        _data = {
            'num': c.num,
            'task_name': c.task_name,
            'task_config_time': c.task_config_time,
            'task_type': c.task_type,
            'set_ids': json.loads(c.set_id),
            'case_ids': json.loads(c.case_id),
            'task_to_email_address': c.task_to_email_address,
            'task_send_email_address': c.task_send_email_address,
            'password': c.email_password,
            'dingding': c.dd_token,
            'test_env': c.task_env
        }
        return jsonify({'data': _data, 'status': 1})
    
    @classmethod
    def find_task(cls, project_name, task_name, page, per_page):
        if not project_name:
            return jsonify({'msg': '请先选择项目', 'status': 0})
        
        project_id = InterfaceProject.query.filter_by(name=project_name,
                                                      status=InterfaceProject.ACTIVE).first().id
        
        if task_name:
            _data = InterfaceTask.query.filter_by(project_id=project_id,
                                                  delete_status=InterfaceTask.ACTIVE).filter(
                InterfaceTask.task_name.like('%{}%'.format(task_name))).all()
            total = len(_data)
            if not _data:
                return jsonify({'msg': '没有该任务', 'status': 0})
        else:
            tasks = InterfaceTask.query.filter_by(project_id=project_id,
                                                  delete_status=InterfaceTask.ACTIVE)
            pagination = tasks.order_by(InterfaceTask.id.asc()).paginate(page, per_page=per_page,
                                                                         error_out=False)
            _data = pagination.items
            total = pagination.total
        task = [{
            'task_name': c.task_name, 'task_config_time': c.task_config_time,
            'id': c.id, 'task_type': c.task_type, 'status': c.status, 'dingding': c.dd_token,
            'test_env': c.task_env, 'run_status': False
        } for c in _data]
        return jsonify({'data': task, 'total': total, 'status': 1})
    
    @classmethod
    def del_task(cls, ids):
        _edit = InterfaceTask.query.filter_by(id=ids, delete_status=InterfaceTask.ACTIVE).first()
        if _edit.status != '创建':
            return jsonify({'msg': '请先移除任务', 'status': 0})
        
        InterfaceTaskBusiness.task_delete(ids)
        return jsonify({'msg': '删除成功', 'status': 1})
    
    @classmethod
    def pause_task(cls, ids):
        _data = InterfaceTask.query.filter_by(id=ids, delete_status=InterfaceTask.ACTIVE).first()
        _data.status = '暂停'
        # jobs = current_app.apscheduler.get_jobs()
        current_app.apscheduler.pause_job(str(ids))  # 暂停任务
        db.session.commit()
        return jsonify({'msg': '暂停成功', 'status': 1})
    
    @classmethod
    def resume_task(cls, ids):
        _data = InterfaceTask.query.filter_by(id=ids, delete_status=InterfaceTask.ACTIVE).first()
        _data.status = '启动'
        current_app.apscheduler.resume_job(str(ids))  # 恢复任务
        db.session.commit()
        return jsonify({'msg': '恢复成功', 'status': 1})
    
    @classmethod
    def remove_task(cls, ids):
        _data = InterfaceTask.query.filter_by(id=ids, delete_status=InterfaceTask.ACTIVE).first()
        try:
            current_app.apscheduler.remove_job(str(ids))  # 删除任务
        except Exception as e:
            current_app.logger.info(str(e))
        _data.status = '创建'
        db.session.commit()
        return jsonify({'msg': '移除成功', 'status': 1})
    
    @classmethod
    def scanner_task(cls):
        first_jobs = current_app.apscheduler.get_jobs()
        task_data = InterfaceTask.query.filter_by(status='启动',
                                                  delete_status=InterfaceProject.ACTIVE).all()
        # 如果数据库定时任务与内存中一致，忽略
        if len(first_jobs) == len(task_data):
            return 0
        # 分别计算内存与sql的任务id，内存中为str(id)->int
        job_list = [int(job.id) for job in first_jobs]
        task_list = [task.id for task in task_data]
        # 找出内存中不存在的定时任务
        base_job = set(task_list).difference(set(job_list))
        # 过滤出已经在内存中的定时任务，取出已失效的
        task_data = InterfaceTask.query.filter(InterfaceTask.status == '启动',
                                               InterfaceTask.id.in_(list(base_job)),
                                               InterfaceTask.delete_status == InterfaceProject.ACTIVE).all()
        
        current_app.logger.info('重启开始扫描定时任务,共{}'.format(str(len(task_data))))
        for task in task_data:
            project_name = InterfaceProject.query.filter_by(id=task.project_id,
                                                            status=InterfaceProject.ACTIVE).first().name
            case_ids = cls.get_case_ids(task)
            config_time = change_cron(task.task_config_time)
            try:
                current_app.apscheduler.add_job(func=cls.aps_test, trigger='cron',
                                                args=[task.project_id, project_name, case_ids,
                                                      task.task_send_email_address,
                                                      task.email_password,
                                                      task.task_to_email_address,
                                                      task.id, task.task_env],
                                                id=str(task.id), **config_time)  # 添加任务
            except Exception as e:
                current_app.logger.error('扫描{}定时任务出错{}'.format(str(task.id), str(e)))
        base_jobs = current_app.apscheduler.get_jobs()
        # 计算更新任务数
        add_jobs = len(base_jobs) - len(first_jobs)
        current_app.logger.info(f'当前共加入{add_jobs}任务')
        return add_jobs
    
    @classmethod
    def get_running_task(cls):
        jobs = current_app.apscheduler.get_jobs()
        if len(jobs) == 0:
            return []
        return [{'id': x.args[0], 'name': x.args[1]} for x in jobs]
        

